package health_system.health_system.service.impl;

import health_system.health_system.dto.*;
import health_system.health_system.entity.Moment;
import health_system.health_system.entity.MomentLike;
import health_system.health_system.entity.User;
import health_system.health_system.exception.BusinessException;
import health_system.health_system.exception.MomentLikeNotFoundException;
import health_system.health_system.exception.MomentNotFoundException;
import health_system.health_system.exception.ResourceNotFoundException;
import health_system.health_system.repository.MomentLikeRepository;
import health_system.health_system.repository.MomentRepository;
import health_system.health_system.repository.UserRepository;
import health_system.health_system.service.MomentLikeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class MomentLikeServiceImpl implements MomentLikeService {

    private final MomentLikeRepository momentLikeRepository;
    private final MomentRepository momentRepository;
    private final UserRepository userRepository;

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentLikes", "userLikes", "likeCount", "momentLikeStatus"}, allEntries = true),
        @CacheEvict(value = "userLikedMoment", allEntries = true)
    })
    public MomentLikeDTO likeMoment(MomentLikeCreateDTO likeCreateDTO) {
        log.debug("Creating like for moment ID: {} by user ID: {}", 
                likeCreateDTO.getMomentId(), likeCreateDTO.getUserId());
        
        // Check if already liked
        User user = userRepository.findById(likeCreateDTO.getUserId())
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", likeCreateDTO.getUserId()));
        
        Moment moment = momentRepository.findById(likeCreateDTO.getMomentId())
                .orElseThrow(() -> new MomentNotFoundException(likeCreateDTO.getMomentId()));
        
        // Check if user has already liked this moment
        if (momentLikeRepository.existsByUserAndMoment(user, moment)) {
            throw new BusinessException("User has already liked this moment");
        }
        
        // Create the like
        MomentLike like = new MomentLike();
        like.setUser(user);
        like.setMoment(moment);
        
        try {
            MomentLike savedLike = momentLikeRepository.save(like);
            log.info("Successfully created like with ID: {} for moment ID: {} by user ID: {}", 
                    savedLike.getId(), moment.getId(), user.getId());
            return MomentLikeDTO.fromEntity(savedLike);
        } catch (DataIntegrityViolationException e) {
            log.error("Failed to create like due to data integrity violation", e);
            throw new BusinessException("Failed to create like. The like might already exist.", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentLikes", "userLikes", "likeCount", "momentLikeStatus"}, allEntries = true),
        @CacheEvict(value = "userLikedMoment", allEntries = true)
    })
    public void unlikeMoment(Long userId, Long momentId) {
        log.debug("Removing like for moment ID: {} by user ID: {}", momentId, userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        // Check if like exists
        if (!momentLikeRepository.existsByUserAndMoment(user, moment)) {
            throw new BusinessException("Like does not exist for this user and moment");
        }
        
        try {
            momentLikeRepository.deleteByUserAndMoment(user, moment);
            log.info("Successfully deleted like for moment ID: {} by user ID: {}", momentId, userId);
        } catch (Exception e) {
            log.error("Failed to delete like for moment ID: {} by user ID: {}", momentId, userId, e);
            throw new BusinessException("Failed to delete like", e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userLikedMoment", key = "#userId + '-' + #momentId")
    public boolean hasUserLikedMoment(Long userId, Long momentId) {
        log.debug("Checking if user ID: {} has liked moment ID: {}", userId, momentId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        return momentLikeRepository.existsByUserAndMoment(user, moment);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentLike", key = "#id")
    public MomentLikeDTO getLikeById(Long id) {
        log.debug("Getting like by ID: {}", id);
        
        MomentLike like = getLikeEntityById(id);
        return MomentLikeDTO.fromEntity(like);
    }

    @Override
    @Transactional(readOnly = true)
    public MomentLike getLikeEntityById(Long id) {
        log.debug("Getting like entity by ID: {}", id);
        
        return momentLikeRepository.findById(id)
                .orElseThrow(() -> new MomentLikeNotFoundException(id));
    }

    @Override
    @Transactional(readOnly = true)
    public Optional<MomentLike> findLikeEntityById(Long id) {
        log.debug("Finding like entity by ID: {}", id);
        
        return momentLikeRepository.findById(id);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userMomentLike", key = "#userId + '-' + #momentId")
    public Optional<MomentLikeDTO> getLikeByUserAndMoment(Long userId, Long momentId) {
        log.debug("Getting like by user ID: {} and moment ID: {}", userId, momentId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        return momentLikeRepository.findByUserAndMoment(user, moment)
                .map(MomentLikeDTO::fromEntity);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentLikes", key = "#momentId")
    public List<MomentLikeDTO> getLikesByMomentId(Long momentId) {
        log.debug("Getting all likes for moment ID: {}", momentId);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        List<MomentLike> likes = momentLikeRepository.findByMomentOrderByCreatedAtDesc(moment);
        
        if (likes.isEmpty()) {
            return Collections.emptyList();
        }
        
        return likes.stream()
                .map(MomentLikeDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentLikesPaged", 
              key = "#momentId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentLikeDTO> getLikesByMomentId(Long momentId, Pageable pageable) {
        log.debug("Getting paginated likes for moment ID: {} with page: {}", 
                momentId, pageable.getPageNumber());
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        Page<MomentLike> likesPage = momentLikeRepository.findByMomentOrderByCreatedAtDesc(moment, pageable);
        
        Page<MomentLikeDTO> dtoPage = likesPage.map(MomentLikeDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userLikes", key = "#userId")
    public List<MomentDTO> getMomentsLikedByUser(Long userId) {
        log.debug("Getting all moments liked by user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<Moment> moments = momentLikeRepository.findMomentsLikedByUser(user);
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(MomentDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userLikesPaged", 
              key = "#userId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentDTO> getMomentsLikedByUser(Long userId, Pageable pageable) {
        log.debug("Getting paginated moments liked by user ID: {} with page: {}", 
                userId, pageable.getPageNumber());
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Page<Moment> momentsPage = momentLikeRepository.findMomentsLikedByUser(user, pageable);
        
        Page<MomentDTO> dtoPage = momentsPage.map(MomentDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "likeCount", key = "'moment:' + #momentId")
    public Long countLikesByMomentId(Long momentId) {
        log.debug("Counting likes for moment ID: {}", momentId);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        return momentLikeRepository.countByMoment(moment);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "likeCount", key = "'user:' + #userId")
    public Long countLikesByUserId(Long userId) {
        log.debug("Counting likes by user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        return momentLikeRepository.countByUser(user);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentLikers", key = "#momentId")
    public List<UserDTO> getUsersWhoLikedMoment(Long momentId) {
        log.debug("Getting all users who liked moment ID: {}", momentId);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        List<User> users = momentLikeRepository.findUsersWhoLikedMoment(moment);
        
        if (users.isEmpty()) {
            return Collections.emptyList();
        }
        
        return users.stream()
                .map(UserDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentLikersPaged", 
              key = "#momentId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<UserDTO> getUsersWhoLikedMoment(Long momentId, Pageable pageable) {
        log.debug("Getting paginated users who liked moment ID: {} with page: {}", 
                momentId, pageable.getPageNumber());
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        Page<User> usersPage = momentLikeRepository.findUsersWhoLikedMoment(moment, pageable);
        
        Page<UserDTO> dtoPage = usersPage.map(UserDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentsWithMostLikes", 
              key = "#viewerId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public List<MomentWithStatsDTO> getMomentsWithMostLikesVisibleToUser(Long viewerId, Pageable pageable) {
        log.debug("Getting moments with most likes visible to user ID: {} with page: {}", 
                viewerId, pageable.getPageNumber());
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        List<Object[]> results = momentLikeRepository.findMomentsWithMostLikesVisibleToUser(viewer, pageable);
        
        if (results.isEmpty()) {
            return Collections.emptyList();
        }
        
        return results.stream()
                .map(result -> {
                    Moment moment = (Moment) result[0];
                    Long likeCount = (Long) result[1];
                    
                    return MomentWithStatsDTO.fromMomentAndStatsWithLikeStatus(moment, likeCount, viewerId);
                })
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "recentlyLikedByFriends", 
              key = "#userId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public List<MomentDTO> getRecentlyLikedMomentsByFriends(Long userId, Pageable pageable) {
        log.debug("Getting recently liked moments by friends of user ID: {} with page: {}", 
                userId, pageable.getPageNumber());
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<Moment> moments = momentLikeRepository.findRecentlyLikedMomentsByFriends(user, pageable);
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(moment -> MomentDTO.fromEntityWithLikeStatus(moment, userId))
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "batchLikeStatus", key = "#userId + '-' + #momentIds.hashCode()")
    public Map<Long, Boolean> batchCheckUserLikedMoments(Long userId, List<Long> momentIds) {
        log.debug("Batch checking if user ID: {} has liked moments: {}", userId, momentIds);
        
        if (momentIds == null || momentIds.isEmpty()) {
            return Collections.emptyMap();
        }
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Map<Long, Boolean> result = new HashMap<>();
        
        // Initialize all as not liked
        momentIds.forEach(momentId -> result.put(momentId, false));
        
        // Get all moments from the repository to avoid N+1 queries
        List<Moment> moments = momentRepository.findAllById(momentIds);
        
        // Find all likes for this user and the given moments in one query
        // This would be a custom query in the repository like:
        // findByUserAndMomentIn(User user, Collection<Moment> moments)
        for (Moment moment : moments) {
            boolean liked = momentLikeRepository.existsByUserAndMoment(user, moment);
            result.put(moment.getId(), liked);
        }
        
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "batchLikeCounts", key = "#momentIds.hashCode()")
    public Map<Long, Long> batchGetLikeCountsForMoments(List<Long> momentIds) {
        log.debug("Batch getting like counts for moments: {}", momentIds);
        
        if (momentIds == null || momentIds.isEmpty()) {
            return Collections.emptyMap();
        }
        
        Map<Long, Long> result = new HashMap<>();
        
        // Get all moments from the repository to avoid N+1 queries
        List<Moment> moments = momentRepository.findAllById(momentIds);
        
        // Count likes for each moment
        // This could be optimized with a custom query in the repository for bulk counting
        for (Moment moment : moments) {
            Long count = momentLikeRepository.countByMoment(moment);
            result.put(moment.getId(), count);
        }
        
        return result;
    }
} 